#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include "job_schd.h"
#include "ipc_def.h"

/*
 * A child created via fork does not inherit its parent's
 * interval timers. Interval timers are preserved across 
 * an execve.
 */
#define JID_POOL_SIZE (MAX_JOB_NUM >> (sizeof(unsigned char) * 3))
#define BIT_PER_ELEMT (1 << (sizeof(unsigned char) * 3))
unsigned char jid_pool[JID_POOL_SIZE] = {0};

int svr_sockfd;
int sta_fd;
struct JobInfo *jq_head, *jq_tail/*, *jq_current*/;
struct itimerval value, ovalue;
unsigned char *sock_buf;
unsigned char *sock_buf_p;

unsigned int alloc_jid(void)
{
    int i, j;
    for (i = 0; i < JID_POOL_SIZE; i++) {
        j = 0;
        while (j < BIT_PER_ELEMT && ((jid_pool[i] >> j) & 1) != 0) {
            j++;
        }
        if (j != BIT_PER_ELEMT) {
            jid_pool[i] |= (1 << j);
            return (BIT_PER_ELEMT * i + j + 1);
        }
    }
    return -1;
}

void reclaim_jid(unsigned int jid)
{
    int i, j;
    i = jid / BIT_PER_ELEMT;
    j = (jid - 1) % BIT_PER_ELEMT;
    jid_pool[i] &= ~(1 << j);
}

void setup_timer(void)
{
    value.it_interval.tv_sec = 0;
    value.it_interval.tv_usec = SCHD_INTV;
    memcpy(&value.it_value, &value.it_interval, sizeof(struct timeval));
    if (setitimer(ITIMER_REAL, &value, &ovalue) < 0) {
        fprintf(stderr, "calling setitimer failed from init: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
}

void queue_add(struct JobInfo *job_info)
{
    struct JobInfo *ji_p;
    /* 
     * using calloc for job_info so that job_info->next
     * and job_info->prev are both NULL.
     * job_info->curr_prior has been initialized to job_info->init_prior.
     */
    if (!jq_head) {
        jq_head = job_info;
        jq_tail = job_info;
        return;
    }
    else {
        ji_p = jq_head;
        while (ji_p) {
            if (ji_p->curr_prior >= job_info->curr_prior) {
                break;
            }
            ji_p = ji_p->next;
        }
        if (!ji_p) {
            /* 
             * job_info has the highest priority, then add it to 
             * the tail of queue.
             */
            jq_tail->next = job_info;
            job_info->prev = jq_tail;
            jq_tail = job_info;
        }
        else {
            /* insert job_info before ji_p. */
            if (ji_p->prev) {
                ji_p->prev->next = job_info;
            }
            else {
                ji_p->prev = job_info;
                jq_head = job_info;
            }
            job_info->next = ji_p;
            /* it's ok if ji_p->prev is null. */
            job_info->prev = ji_p->prev;
            ji_p->prev = job_info;
        }
    }
}

void queue_remove(struct JobInfo *job_info)
{
    if (job_info->prev) {
        job_info->prev->next = job_info->next;
    }
    else {
        jq_head = job_info->next;
        /* if job_info is not at the end of the job queue. */
        if (jq_head) {
            jq_head->prev = NULL;
        }
    }
    if (job_info->next) {
        job_info->next->prev = job_info->prev;
    }
    else {
        jq_tail = job_info->prev;
        /* if job_info is not at the head of the job queue */
        if (jq_tail) {
            jq_tail->next = NULL;
        }
    }
    reclaim_jid(job_info->jid);
    FREE_ARRAY(job_info->argv, job_info->argc);
    free(job_info);
}

struct JobInfo *queue_find_by_jid(unsigned int jid)
{
    struct JobInfo *ji_p;

    ji_p = jq_head;
    while (ji_p) {
        if (ji_p->jid == jid) {
            return ji_p;
        }
    }
    return NULL;
}

struct JobInfo *queue_find_by_pid(pid_t pid)
{
    struct JobInfo *ji_p;

    ji_p = jq_head;
    while (ji_p) {
        if (ji_p->pid == pid) {
            return ji_p;
        }
    }
    return NULL;
}

void queue_update(struct JobInfo *job_info)
{
    /* 
     * It must have at least one element job_info.
     * If job_info is the only element or no other jobs
     * have a lower or equal priority than job_info, then
     * the function will just return without doing any 
     * thing special.
     */
    struct JobInfo *ji_p;

    ji_p = jq_tail;
    while (ji_p) {
        if (ji_p == job_info) {
            return;
        }
        if (ji_p->curr_prior <= job_info->curr_prior) {
            break;
        }
        ji_p = ji_p->prev;
    }
    /*
     * Update the connection at the original position.
     * When reaching here, it means that it's not possible
     * is at the end of the queue whose ->next is null.
     */
    if (job_info->prev) {
        job_info->prev->next = job_info->next;
        job_info->next->prev = job_info->prev;
    }
    else {
        jq_head = job_info->next;
        jq_head->prev = NULL;
    }
    /* add job_info after ji_p */
    if (ji_p) {
        job_info->next = ji_p->next;
        if (ji_p->next) {
            ji_p->next->prev = job_info;
        }
        else {
            jq_tail = job_info;
            jq_tail->next = NULL;
        }
        job_info->prev = ji_p;
        ji_p->next = job_info;
    }
    /* ji_p is before head of the queue, i.e. null. */
    else {
        jq_head->prev = job_info;
        job_info->next = jq_head;
        job_info->prev = NULL;
        jq_head = job_info;
    }
}

int reap(pid_t pid, int *stat_loc, int options)
{
    pid_t           t_pid;
    struct JobInfo  *ji_p;

    do {
        t_pid = waitpid(pid, stat_loc, options);
        if (t_pid < 0) {
            switch(errno) {
                /*
                 * The process specified by pid does not exist or is not
                 * a child of the calling process, or the process group
                 * specified by pid does not exist or does not have any
                 * member process that is a child of the calling process.
                 */
                case ECHILD:
                    return 0;
                /*
                 * The function was interrupted by a signal. The value of
                 * the location pointed by stat_loc is undefined.
                 */
                case EINTR:
                    continue;
                    break;
                /*
                 * The options argument is not valid.
                 */
                case EINVAL:
                    return t_pid;
                default:
                    break;
            }
        }
        /*
         * If waitpid() was invoked with WNOHANG set in options, it has at
         * least ne chid process specified by pid for which status is not 
         * available, and status is not available for any process specified
         * by pid, 0 is returned.
         */
        else if (t_pid == 0) {
            return 0;
        }
        else {
            ji_p = queue_find_by_pid(t_pid);
            if (WIFEXITED(*stat_loc) ||
                    WIFSIGNALED(*stat_loc)) {
                queue_remove(ji_p);
            }
            else if (WIFSTOPPED(*stat_loc)) {
                /*
                 * distinguish it from being stopped by 
                 * scheduler.
                 * lies. ;-)
                 */
                if (ji_p->state == JS_EXTRA) {
                    ji_p->state = JS_READY;
                }
                else {
                    ji_p->state = JS_STOPPED;
                }
            }
#ifdef WIFCONTINUED
            else if(WIFCONTINUED(*stat_loc)) {
                /*
                 * continued? by whom?
                 * it should not be possible for any job reach
                 * this far since any CONTINUED should have been 
                 * sent SIGCONT.
                 * Then the continuing factor must coming from
                 * somewhere else. Ohhh. get to JS_READY.
                 */
                ji_p->state = JS_READY;
                kill(ji_p->pid, SIGSTOP);
            }
#endif
            else {
                /* maybe this is a bug. */
                continue;
            }
        }
    } while (1);
}

void enq(struct JobInfo *job_info)
{
    pid_t pid;

    if ((pid = fork()) < 0) {
        return;
    }
    else if (pid == 0) {
        raise(SIGSTOP);
        /* 
         * permission check has been done by scheduler by
         * calling check_permission.
         */
        execv(job_info->argv[0], job_info->argv);
    }
    else {
        gettimeofday(&job_info->crea_time, NULL);
        job_info->pid = pid;
        job_info->state = JS_EXTRA;
        queue_add(job_info);
    }
}

void deq(struct JobInfo *job_info)
{
    /* 
     * just kill it. no check is needed.
     */
    kill(job_info->pid, SIGKILL);
    queue_remove(job_info);
}

void handle_enq(int cli_sockfd, uid_t euid)
{
    struct tag_struct tag;
    unsigned char state = 0;
    unsigned char prior;
    char *argv[ARGS_NUM] = {0};
    unsigned int argc = 0;
    struct JobInfo *ji_p;

    while (recv_tag_from_buf(&tag, sock_buf, &sock_buf_p) >= 0) {
        if (state == 0 && tag.tag_type == TAG_UID) {
            state = 1;
        }
        else if (state == 1 && tag.tag_type == TAG_PRIOR) {
            state = 2;
            prior = tag.tag_value[0];
            /* 
             * if it is an valid prior, then reset it to zero.
             * or, IS_VALID_PRIOR return the original priority
             * value.
             */
            prior = IS_VALID_PRIOR(prior);
        }
        else if (state == 2 && tag.tag_type == 0x83/*TAG_PATH*/) {
            state = 3;
            TAG_TO_ARRAY(tag, argv, argc);
        }
        else if (state == 3 && tag.tag_type == TAG_ARGS) {
            TAG_TO_ARRAY(tag, argv, argc);
        }
        else if (state == 3 && tag.tag_type == TAG_END) {
            break;
        }
        else {
            fprintf(stderr, "received an unvalid packet.\n");
            return;
        }
    }

    ji_p = (struct JobInfo *)calloc(1, sizeof(struct JobInfo));
    if (check_permission(argv[0], euid) < 0) {
        return;
    }
    ji_p->euid = euid;
    _getusername(euid, ji_p->username, BUF_SIZE);
    ji_p->argc = argc;
    memcpy(ji_p->argv, argv, sizeof(unsigned char *) * argc);
    ji_p->curr_prior = ji_p->init_prior = prior;
    ji_p->jid = alloc_jid();
    enq(ji_p);
}

void handle_deq(int cli_sockfd, uid_t euid)
{
    struct tag_struct tag;
    unsigned char state = 0;
    unsigned int jid;
    struct JobInfo *ji_p;

    while (recv_tag_from_buf(&tag, sock_buf, &sock_buf_p) >= 0) {
        if (state == 0 && tag.tag_type == TAG_UID) {
            state = 1;
        }
        else if (state == 1 && tag.tag_type == TAG_JID) {
            state = 2;
            memcpy(&jid, tag.tag_value, MIN(tag.tag_len, sizeof(unsigned int)));
        }
        else if (state == 2 && tag.tag_type == TAG_END) {
            break;
        }
        else {
            return;
        }
    }
    if (!(ji_p = queue_find_by_jid(jid))) {
        if (ji_p->euid == euid || euid == ROOT_UID) {
            deq(ji_p);
        }
    }
    return;
}

void handle_sta(void)
{
    struct JobInfo *ji_p;

    lseek(sta_fd, SEEK_SET, 0);
    ji_p = jq_head;
    while (ji_p) {
        print_job_info(sta_fd, ji_p);
        ji_p = ji_p->next;
    }
}

void accept_and_handle(void)
{
    int cli_sockfd;
    uid_t euid;
    struct tag_struct op_buf;

    while ((cli_sockfd = init_acpt(svr_sockfd, &euid)) >= 0 &&
            recv_to_buf(cli_sockfd, sock_buf, SOCK_BUF_SIZE) >= 0 &&
            recv_tag_from_buf(&op_buf, sock_buf, &sock_buf_p) >= 0) {
        if (op_buf.tag_type == TAG_OPCODE) {
            switch ((int)op_buf.tag_value[0]) {
                case OP_ENQ:
                    handle_enq(cli_sockfd, euid);
                    break;
                case OP_DEQ:
                    handle_deq(cli_sockfd, euid);
                    break;
                case OP_STA:
                    handle_sta();
                    break;
            }
        }
        sock_buf_p = sock_buf;
        close(cli_sockfd);
    }
}

void refresh(void)
{
    struct JobInfo *ji_p, *just_exectd;
    unsigned long schd_cost;
    int stat_loc;
    /* set a new timer */
    memset(&value, 0, sizeof(struct itimerval));
    gettimeofday(&ovalue.it_value, NULL);
    /* update executed time and reset priority. */
    if (jq_tail) {
        kill(SIGSTOP, jq_tail->pid);
        jq_tail->state = JS_EXTRA;
        TVADDUSEC(jq_tail->exec_time, SCHD_INTV);
        jq_tail->curr_prior = jq_tail->init_prior;
    }
    /* 
     * record the just job just stopped except it
     * from updating waiting time.
     */
    just_exectd = jq_tail;
    /* update total waiting time and refresh priority. */
    ji_p = jq_head;
    while (ji_p != just_exectd) {
        TVADDUSEC(ji_p->wait_time, SCHD_INTV);
        TVADDUSEC(ji_p->cuwa_time, SCHD_INTV);
        if (TV2MS(ji_p->cuwa_time) >= SC_INTV) {
            TVCLEAR(ji_p->cuwa_time);
            ji_p->curr_prior += (ji_p->curr_prior < 3 ? 1 : 0);
            queue_update(ji_p);
        }
        ji_p = ji_p->next;
/*#ifdef _BSD_SOURCE
        timeradd(&ji_p->wait_time, );
        ji_p->wait_time += 10;
#endif*/
    }
    /* handle requests */
    accept_and_handle();
    /* 
     * requests are handled, then reap signal information
     * from child.
     */
    reap(-1, &stat_loc, WCONTINUED | WNOHANG | WUNTRACED);

    gettimeofday(&ovalue.it_interval, NULL);
    schd_cost = TV2USEC(ovalue.it_interval, ovalue.it_value);
    ji_p = jq_head;
    while (ji_p) {
        TVADDUSEC(ji_p->wait_time, schd_cost);
        TVADDUSEC(ji_p->cuwa_time, schd_cost);
        ji_p = ji_p->next;
    }
    /* restart original timer */
    setup_timer();
    if (jq_tail) {
        TVCLEAR(jq_tail->cuwa_time);
        jq_tail->state = JS_RUNNING;
        kill(jq_tail->pid, SIGCONT);
    }
}

void sig_routine(int signo)
{
    switch (signo) {
        case SIGALRM:
            refresh();
            break;
/*        case SIGCHLD:
            reap(-1, &stat_loc, WCONTINUED | WNOHANG | WUNTRACED);
            break;
            */
    }
}

int set_nonblocking(int sockfd)
{
    int flag;

#ifdef O_NONBLOCK
    if ((flag = fcntl(sockfd, F_GETFL, 0)) == -1) {
        flag = 0;
    }
    return fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);
#else
    flag = 1;
    return ioctl(sockfd, FIOBIO, &flags);
#endif
}

void init(void)
{
    struct termios term;

    sock_buf = calloc(1, SOCK_BUF_SIZE);
    sock_buf_p = sock_buf;

    /*
     * scheduler will just ignore SIGCHLD but the handler
     * can not be set to SIG_IGN because he want to reap
     * information for zombie proceses.
     */
    if (signal(SIGTTOU, SIG_IGN) == SIG_ERR ||
            signal(SIGCHLD, SIG_DFL) == SIG_ERR ||
            signal(SIGALRM, sig_routine) == SIG_ERR) {
        fprintf(stderr, "calling signal from init failed: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* Enable output if scheduler is running at background. */
    tcgetattr(STDERR_FILENO, &term);
    if (!(term.c_lflag & TOSTOP)) {
        term.c_lflag |= TOSTOP;
        tcsetattr(STDERR_FILENO, TCSADRAIN, &term);
    }

    jq_head = jq_tail = /*jq_current =*/ NULL;

    if ((svr_sockfd = init_lisn()) < 0) {
        fprintf(stderr, "calling init_lisn from init failed: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    /*
     * set to nonblocking for accept
     * asynchronous io maybe preferable
     */
    set_nonblocking(svr_sockfd);
    if ((sta_fd = open(STAT_OUT_FILE, O_WRONLY | O_CREAT | O_TRUNC)) < 0) {
        fprintf(stderr, "calling open from init failed.\n");
        exit(EXIT_FAILURE);
    }

    setup_timer();
}

int main(int argc, char *argv[])
{
    init();
    /* SIGALRM will wake me up. */
    while (1) {
       sleep(1);
    }
    return 0;
}

